package com.zhaoziqian.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.protobuf.ByteString;
import com.google.protobuf.Descriptors;
import com.zhaoziqian.client.ChannelClient;
import com.zhaoziqian.client.FabricClient;
import com.zhaoziqian.model.AdminModel;
import com.zhaoziqian.model.OrdererModel;
import com.zhaoziqian.model.PeerModel;
import com.zhaoziqian.model.SDKResponse;
import com.zhaoziqian.service.BlockService;
import com.zhaoziqian.user.UserContext;
import com.zhaoziqian.utils.HexByteUtil;
import com.zhaoziqian.utils.ReadNetworkConfig;
import com.zhaoziqian.utils.Util;
import org.hyperledger.fabric.protos.common.Common;
import org.hyperledger.fabric.protos.peer.FabricTransaction;
import org.hyperledger.fabric.protos.peer.Query;
import org.hyperledger.fabric.sdk.*;
import org.hyperledger.fabric.sdk.exception.CryptoException;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.exception.TransactionException;
import org.hyperledger.fabric.sdk.security.CryptoSuite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.util.*;

import static com.zhaoziqian.config.Config.*;
import static com.zhaoziqian.config.Config.ADMIN;

/**
 * Created with fabric-cli
 * 作者： zhaoziqian
 * 时间： 2018/6/4  上午10:55
 * 功能：
 */
@Service
public class BlockServiceImpl implements BlockService {

    private Logger logger = LoggerFactory.getLogger(BlockServiceImpl.class);

    @Override
    public SDKResponse getBlock(String ordererOrg, String ordererId, String org, String peerId, String channelName, String blockNumber) throws Exception {
        logger.info("get BlockInfo from channel {}", channelName);
        FabricClient fabricClient = getFabricClient(org);
        ChannelClient channelClient = getChannelClient(fabricClient, ordererOrg, ordererId, org, peerId, channelName);

        BlockInfo blockInfo = channelClient.queryBlockByNumber(Long.parseLong(blockNumber));

        JSONObject resJson = new JSONObject();

        Common.Block block = blockInfo.getBlock();
        Common.BlockHeader header = block.getHeader();
        Common.BlockData data = block.getData();
        Common.BlockMetadata metadata = block.getMetadata();

        JSONObject headerJson = new JSONObject();
//        headerJson.put("previousHash", header.getPreviousHash().toStringUtf8());
//        headerJson.put("dataHash", header.getDataHash().toStringUtf8());
        headerJson.put("previousHash", HexByteUtil.bytesToHexString(header.getPreviousHash().toByteArray()));
        headerJson.put("dataHash", HexByteUtil.bytesToHexString(header.getDataHash().toByteArray()));
        headerJson.put("number", header.getNumber());

        JSONObject dataJson = new JSONObject();
        JSONArray jsonList = new JSONArray();
        for (ByteString bs : data.getDataList()) {
            jsonList.add(HexByteUtil.bytesToHexString(bs.toByteArray()));
//            jsonList.add(bs.toStringUtf8());
        }
        dataJson.put("data", jsonList);

        JSONObject metadataJson = new JSONObject();
        jsonList = new JSONArray();
        for (ByteString bs : metadata.getMetadataList()) {
            jsonList.add(HexByteUtil.bytesToHexString(bs.toByteArray()));
//            jsonList.add(bs.toStringUtf8());
        }
        metadataJson.put("metadata", jsonList);

        JSONObject blockJson = new JSONObject();
        blockJson.put("header", headerJson);
        blockJson.put("data", dataJson);
        blockJson.put("metadata", metadataJson);

        resJson.put("block", blockJson);
        resJson.put("blockNumber", blockInfo.getBlockNumber());
        resJson.put("channelId", blockInfo.getChannelId());
        resJson.put("dataHash", HexByteUtil.bytesToHexString(blockInfo.getDataHash()));

        resJson.put("envelopCount", blockInfo.getEnvelopCount());
        Iterable<BlockInfo.EnvelopeInfo> envelopeInfos = blockInfo.getEnvelopeInfos();
        JSONArray enveList = new JSONArray();
        envelopeInfos.forEach(envelopeInfo -> {
            JSONObject object = new JSONObject();
            object.put("channelId", envelopeInfo.getChannelId());
            object.put("epoch", envelopeInfo.getEpoch());
            object.put("timestamp", envelopeInfo.getTimestamp());
            object.put("transactionID", envelopeInfo.getTransactionID());
            object.put("type", envelopeInfo.getType());
            object.put("validationCode", envelopeInfo.getValidationCode());
            enveList.add(object);
        });
        resJson.put("envelopeInfos", enveList);
        resJson.put("previousHash", HexByteUtil.bytesToHexString(blockInfo.getPreviousHash()));
        JSONArray actionList = new JSONArray();
        blockInfo.getTransactionActionInfos().forEach(actionInfo -> {
            JSONObject object = new JSONObject();
            object.put("channelId", actionInfo.getChannelId());
            object.put("epoch", actionInfo.getEpoch());
            object.put("timestamp", actionInfo.getTimestamp());
            object.put("transactionID", actionInfo.getTransactionID());
            object.put("type", actionInfo.getType());
            object.put("validationCode", actionInfo.getValidationCode());
            actionList.add(object);
        });
        resJson.put("transactionActionInfos", actionList);

        logger.debug("block : {}", blockInfo.getBlock());
        logger.debug("blockNumber : {}", blockInfo.getBlockNumber());
        logger.debug("channelId : {}", blockInfo.getChannelId());
        logger.debug("dataHash : {}", blockInfo.getDataHash());
        logger.debug("envelopCount : {}", blockInfo.getEnvelopCount());
        logger.debug("envelopeInfo0 : {}", blockInfo.getEnvelopeInfo(0));
        logger.debug("envelopeInfos : {}", blockInfo.getEnvelopeInfos());
        logger.debug("previousHash : {}", blockInfo.getPreviousHash());
        logger.debug("transactionActionInfos : {}", blockInfo.getTransactionActionInfos());
        logger.debug("transActionsMetaData : {}", blockInfo.getTransActionsMetaData());


        return SDKResponse.OK(resJson.toJSONString());
    }

    @Override
    public SDKResponse queryTransaction(String ordererOrg, String ordererId, String org, String peerId, String channelName, String txId) throws Exception {
        FabricClient fabricClient = getFabricClient(org);
        ChannelClient channelClient = getChannelClient(fabricClient, ordererOrg, ordererId, org, peerId, channelName);
        TransactionInfo transactionInfo = channelClient.queryTransactionByID(txId);
        logger.debug("Envelope : {}", transactionInfo.getEnvelope());
        logger.debug("ProcessedTransaction : {}", transactionInfo.getProcessedTransaction());
        logger.debug("TransactionID : {}", transactionInfo.getTransactionID());
        logger.debug("ValidationCode : {}", transactionInfo.getValidationCode());
        JSONObject resJson = new JSONObject();
        Common.Envelope envelope = transactionInfo.getEnvelope();
        JSONObject enveJson = new JSONObject();
//        envelope.getAllFields().keySet().forEach(fieldDescriptor -> {
//            System.out.println(fieldDescriptor.getJsonName());
//            System.out.println(fieldDescriptor.getName());
//            System.out.println(fieldDescriptor.getFullName());
//        });
        enveJson.put("payload", envelope.getPayload().toStringUtf8());
        enveJson.put("signature", envelope.getSignature().toStringUtf8());
        FabricTransaction.ProcessedTransaction processedTransaction = transactionInfo.getProcessedTransaction();
        resJson.put("envelope", enveJson);
        resJson.put("processedTransaction", transactionInfo.getProcessedTransaction().toByteString().toStringUtf8());
        resJson.put("transactionID", transactionInfo.getTransactionID());
        resJson.put("validationCode", transactionInfo.getValidationCode());
        return SDKResponse.OK(JSON.toJSONString(resJson));
    }

    @Override
    public SDKResponse queryInfo(String ordererOrg, String ordererId, String org, String peerId, String channelName) throws Exception {
        FabricClient fabricClient = getFabricClient(org);
        ChannelClient channelClient = getChannelClient(fabricClient, ordererOrg, ordererId, org, peerId, channelName);
        Peer peer = getPeer(org, peerId, fabricClient);
        channelClient.getChannel().addPeer(peer);
        BlockchainInfo blockchainInfo = channelClient.queryBlockchainInfo(peer);
        logger.debug("CurrentBlockHash", blockchainInfo.getCurrentBlockHash());
        logger.debug("Height", blockchainInfo.getHeight());
        logger.debug("PreviousBlockHash", blockchainInfo.getPreviousBlockHash());

        JSONObject resJson = new JSONObject();
        resJson.put("currentBlockHash", HexByteUtil.bytesToHexString(blockchainInfo.getCurrentBlockHash()));
        resJson.put("height", blockchainInfo.getHeight());
        resJson.put("previousBlockHash", HexByteUtil.bytesToHexString(blockchainInfo.getPreviousBlockHash()));

        return SDKResponse.OK(JSON.toJSONString(resJson));
    }

    @Override
    public SDKResponse queryInstalledChaincodes(String ordererOrg, String ordererId, String org, String peerId, String channelName) throws Exception {
        FabricClient fabricClient = getFabricClient(org);
        HFClient hfClient = fabricClient.getInstance();
        Peer peer = getPeer(org, peerId, fabricClient);
        List<Query.ChaincodeInfo> chaincodeInfos = hfClient.queryInstalledChaincodes(peer);

        JSONArray jsonArray = new JSONArray();
        chaincodeInfos.forEach(chaincodeInfo -> {
            logger.debug("-------------------------------------------------");
            logger.debug("escc", chaincodeInfo.getEscc());
            logger.debug("input", chaincodeInfo.getInput());
            logger.debug("name", chaincodeInfo.getName());
            logger.debug("path", chaincodeInfo.getPath());
            logger.debug("version", chaincodeInfo.getVersion());
            logger.debug("vscc", chaincodeInfo.getVscc());
            logger.debug("-------------------------------------------------");
            JSONObject object = new JSONObject();
            object.put("escc", chaincodeInfo.getEscc());
            object.put("input", chaincodeInfo.getInput());
            object.put("name", chaincodeInfo.getName());
            object.put("path", chaincodeInfo.getPath());
            object.put("version", chaincodeInfo.getVersion());
            object.put("vscc", chaincodeInfo.getVscc());
            jsonArray.add(object);
        });

        return SDKResponse.OK(JSON.toJSONString(jsonArray));
    }

    @Override
    public SDKResponse queryInstantiatedChaincodes(String ordererOrg, String ordererId, String org, String peerId, String channelName) throws Exception {
        FabricClient fabricClient = getFabricClient(org);
        ChannelClient channelClient = getChannelClient(fabricClient, ordererOrg, ordererId, org, peerId, channelName);
        Peer peer = getPeer(org, peerId, fabricClient);
        channelClient.getChannel().addPeer(peer);
        List<Query.ChaincodeInfo> chaincodeInfos = channelClient.queryInstantiatedChaincodes(peer);
        JSONArray jsonArray = new JSONArray();
        chaincodeInfos.forEach(chaincodeInfo -> {
            logger.debug("-------------------------------------------------");
            logger.debug("escc", chaincodeInfo.getEscc());
            logger.debug("input", chaincodeInfo.getInput());
            logger.debug("name", chaincodeInfo.getName());
            logger.debug("path", chaincodeInfo.getPath());
            logger.debug("version", chaincodeInfo.getVersion());
            logger.debug("vscc", chaincodeInfo.getVscc());
            logger.debug("-------------------------------------------------");
            JSONObject object = new JSONObject();
            object.put("escc", chaincodeInfo.getEscc());
            object.put("input", chaincodeInfo.getInput());
            object.put("name", chaincodeInfo.getName());
            object.put("path", chaincodeInfo.getPath());
            object.put("version", chaincodeInfo.getVersion());
            object.put("vscc", chaincodeInfo.getVscc());
            jsonArray.add(object);
        });
        return SDKResponse.OK(JSON.toJSONString(jsonArray));
    }

    @Override
    public SDKResponse queryChannels(String ordererOrg, String ordererId, String org, String peerId, String channelName) throws Exception {
        FabricClient fabricClient = getFabricClient(org);
        HFClient hfClient = fabricClient.getInstance();
        Peer peer = getPeer(org, peerId, fabricClient);
        Set<String> strings = hfClient.queryChannels(peer);
        return SDKResponse.OK(JSON.toJSONString(strings));
    }

    @Override
    public SDKResponse getPeers(String ordererOrg, String ordererId, String org, String peerId, String channelName) throws Exception {
        FabricClient fabricClient = getFabricClient(org);
        ChannelClient channelClient = getChannelClient(fabricClient, ordererOrg, ordererId, org, peerId, channelName);
        Collection<Peer> peers = channelClient.getPeers();
        return SDKResponse.OK(JSON.toJSONString(peers));
    }

    private UserContext getIdentity(String org) throws Exception {

        CryptoSuite.Factory.getCryptoSuite();
        Util.cleanUp();
        // get org admin info
        AdminModel adminModel = ReadNetworkConfig.getAdmin(NOT_ORDERER, org);
        // Construct Channel
        UserContext user = new UserContext();
        // load pk/ca file
        File pkFolder = new File(adminModel.getKeyPath());
        File[] pkFiles = pkFolder.listFiles();
        File certFolder = new File(adminModel.getCertPath());
        File[] certFiles = certFolder.listFiles();
        Enrollment enrollAdmin = Util.getEnrollment(adminModel.getKeyPath(), pkFiles[0].getName(),
                adminModel.getCertPath(), certFiles[0].getName());
        user.setEnrollment(enrollAdmin);
        user.setMspId(ReadNetworkConfig.getParam(NETWORK_CONFIG, PEER, org, MSPID));
        user.setName(ADMIN);

        return user;
    }

    private FabricClient getFabricClient(String org) throws Exception {
        UserContext user = getIdentity(org);
        FabricClient fabClient = new FabricClient(user);
        return fabClient;
    }

    private ChannelClient getChannelClient(FabricClient fabClient, String ordererOrg, String ordererId, String org, String peerId, String channelName) throws Exception {
        Channel channel = fabClient.getInstance().newChannel(channelName);

        OrdererModel ordererModel = ReadNetworkConfig.getOrderer(ordererOrg, ordererId);
        Orderer orderer = Util.createOrderer(fabClient, ordererModel);
        PeerModel peerModel = ReadNetworkConfig.getPeer(org, peerId);
        Peer peer = Util.createPeer(fabClient, peerModel);
        channel.addOrderer(orderer);
        channel.addPeer(peer);
        channel.initialize();

        return new ChannelClient(channelName, channel, fabClient);
    }

    private Peer getPeer(String org, String peerId, FabricClient fabricClient) throws MalformedURLException, InvalidArgumentException {
        return Util.createPeer(fabricClient, ReadNetworkConfig.getPeer(org, peerId));
    }
}
